#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>

#define IO_LEARN_MAJOR 256
#define IO_LEARN_MINOR 0
#define IO_LEARN_CNT 1
#define IO_LEARN_NAME "io-learn"

static DEFINE_KFIFO(iolearnfifo, char, 32);

struct io_learn
{
    struct kfifo *fifo;
    struct cdev cdev;
};

static struct io_learn iodev;

static int io_learn_open(struct inode *inode, struct file *filp)
{
    filp->private_data = container_of(inode->i_cdev, struct io_learn, cdev);

    return 0;
}

static int io_learn_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t io_learn_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    int ret;
    unsigned int copied = 0;

    if (kfifo_is_empty(&iolearnfifo))
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
    ret = kfifo_to_user(&iolearnfifo, buf, count, &copied);

    return ret == 0 ? copied : ret;
}

static ssize_t io_learn_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
    int ret;
    unsigned int copied = 0;

    if (kfifo_is_full(&iolearnfifo))
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;

    ret = kfifo_from_user(&iolearnfifo, buf, count, &copied);

    return ret == 0 ? copied : ret;
}

static struct file_operations io_learn_ops = {
    .owner = THIS_MODULE,
    .open = io_learn_open,
    .release = io_learn_release,
    .read = io_learn_read,
    .write = io_learn_write,
};

static int __init io_learn_init(void)
{

    int ret;
    dev_t dev;

    dev = MKDEV(IO_LEARN_MAJOR, IO_LEARN_MINOR);

    ret = register_chrdev_region(dev, IO_LEARN_CNT, IO_LEARN_NAME);
    if (ret)
        goto reg_err;

    cdev_init(&iodev.cdev, &io_learn_ops);
    iodev.cdev.owner = THIS_MODULE;
    iodev.fifo = (struct kfifo *)&iolearnfifo;

    ret = cdev_add(&iodev.cdev, dev, IO_LEARN_CNT);
    if (ret)
        goto add_err;

add_err:
    unregister_chrdev_region(dev, IO_LEARN_CNT);
reg_err:
    return ret;
}

static void __exit io_learn_exit(void)
{
    dev_t dev;

    dev = MKDEV(IO_LEARN_MAJOR, IO_LEARN_MINOR);

    cdev_del(&iodev.cdev);
    unregister_chrdev_region(dev, IO_LEARN_CNT);
}

module_init(io_learn_init);
module_exit(io_learn_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Biven Tang <bivent@163.com>");
MODULE_DESCRIPTION("Learn non-block io");
MODULE_ALIAS("non-block-io");
